/**
 * Copyright &copy; 2013-2016 山东易科德软件有限公司 All rights reserved.
 */
package cn.net.ecode.modules.cms.utils;

import java.net.URL;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cn.net.ecode.common.config.Global;
import cn.net.ecode.common.utils.DateUtils;
import cn.net.ecode.common.utils.Encodes;
import cn.net.ecode.common.utils.IdGen;
import cn.net.ecode.common.utils.IpUtils;
import cn.net.ecode.common.utils.StringUtils;
import cn.net.ecode.modules.cms.entity.VisitLog;
import cn.net.ecode.modules.sys.entity.User;
import cn.net.ecode.modules.sys.utils.UserUtils;
import eu.bitwalker.useragentutils.DeviceType;
import eu.bitwalker.useragentutils.OperatingSystem;
import eu.bitwalker.useragentutils.UserAgent;

/**
 * 访问收集
 * @author ThinkGem
 * @version 2015年12月24日
 */
public class VisitLogCollect {

	private final int VISIT_DURATION = 1800; // 30分钟

	private final int VISITOR_MAX_AGE = 31536000; // 1年

	private final String[][] SEARCH_ENGINE_LIST = {
			{ "1", "baidu.com", "word|wd|w" },
			{ "2", "google.com", "q" },
			{ "4", "sogou.com", "query" },
			{ "6", "search.yahoo.com", "p" },
			{ "7", "yahoo.cn", "q" },
			{ "8", "soso.com", "w" },
			{ "11", "youdao.com", "q" },
			{ "12", "gougou.com", "search" },
			{ "13", "bing.com", "q" },
			{ "14", "so.com", "q" },
			{ "14", "so.360.cn", "q" },
			{ "15", "jike.com", "q" },
			{ "16", "qihoo.com", "kw" },
			{ "17", "etao.com", "q" },
			{ "18", "soku.com", "keyword" },
			{ "19", "easou.com", "q" },
			{ "20", "glb.uc.cn", "keyword|word|q" } };

	private String[] domainName = {};

	private HttpServletRequest request = null;
	private HttpServletResponse response = null;

	/**
	 * 构造方法
	 */
	public VisitLogCollect(HttpServletRequest request, HttpServletResponse response) {
		this.request = request;
		this.response = response;
		this.domainName = new String[]{request.getServerName()};
	}

	/**
	 * 输出visitLog对象
	 * @param visitLog 解析前
	 * @return 解析后的访问日志对象
	 */
	public VisitLog outputVisitLog(VisitLog visitLog) {
		
		// 获取请求和来源地址和主机
		String requestUrl = Encodes.urlDecode(Encodes.unescapeHtml(visitLog.getRequestUrl()));
		String sourceReferer = Encodes.urlDecode(Encodes.unescapeHtml(visitLog.getSourceReferer()));
		String requestUrlHost = "";
		String sourceRefererHost = "";
		try {
			requestUrlHost = (new URL(requestUrl)).getHost();
			sourceRefererHost = (new URL(sourceReferer)).getHost();
		} catch (Exception e) {
			// 什么也不做
		}
		visitLog.setRequestUrl(requestUrl);
		visitLog.setSourceReferer(sourceReferer);
		visitLog.setRequestUrlHost(requestUrlHost);
		visitLog.setSourceRefererHost(sourceRefererHost);
		
		// 当前访问页面时间
		long currentPageVisitTime = System.currentTimeMillis() / 1000;
		visitLog.setVisitTime(new Date());
		visitLog.setVisitDate(DateUtils.getDate("yyyyMMdd"));
		
		// 当前页面访问的用户
		if (visitLog.getVisitUserCode() == null){
			User user = UserUtils.getUser();
			if (user != null && user.getUserCode() != null){
				visitLog.setVisitUserCode(user.getUserCode());
				visitLog.setVisitUserName(user.getUserName());
			}
		}
		
		// 获取访客唯一标识（若没有，则生成一个）
		String uniqueVisitId = this.getCookie("uniqueVisitId");
		if (StringUtils.isBlank(uniqueVisitId)){
			uniqueVisitId = IdGen.uuidLong();
			this.setCookie("uniqueVisitId", uniqueVisitId, Integer.MAX_VALUE);
		}
		visitLog.setUniqueVisitId(uniqueVisitId);

		// 当前浏览页面时的时间，每次浏览时设置该cookie为当前时间。
		String lpvtCookie = this.getCookie("lastPageVisitTime");
		long lastPageVisitTime = lpvtCookie != "" ? Long.parseLong(lpvtCookie) : 0;
		this.setCookie("lastPageVisitTime", "" + currentPageVisitTime);
		
		// 访问来源（1 直接访问，这次与上次的访问时间大于30分      2 来自搜索引擎      3 外部链接      4 内部访问，这次与上次访问的时间小于30分）
		String sourceType = this.getSourceType(visitLog, currentPageVisitTime, lastPageVisitTime);
		visitLog.setSourceType(sourceType);
		
		// 获取客户IP
		visitLog.setRemoteAddr(IpUtils.getRemoteAddr(request));
		
		// 是否新访客（0 旧访客   1  新访问）
		String isNewVisit = ("4".equals(sourceType)) ? Global.NO : Global.YES;
		visitLog.setIsNewVisit(isNewVisit);

		// 新访客的第一次访问时间， 如果新访客则更新第一次访问时间
		String fvtCookie = this.getCookie("firstVisitTime");
		long firstVisitTime = fvtCookie != "" ? Long.parseLong(fvtCookie) : 0;
		if (Global.YES.equals(isNewVisit)){
			firstVisitTime = currentPageVisitTime;
			this.setCookie("firstVisitTime", "" + firstVisitTime, this.VISITOR_MAX_AGE);
		}
		visitLog.setFirstVisitTime(firstVisitTime);
		
		// 在上一个页面停留时间
		long prevRemainTime = lastPageVisitTime != 0 ? currentPageVisitTime - lastPageVisitTime : 0;
		visitLog.setPrevRemainTime(prevRemainTime);
		
		// 本次访问页面停留时间
		long totalRemainTime = firstVisitTime != 0 ? currentPageVisitTime - firstVisitTime : 0;
		visitLog.setTotalRemainTime(totalRemainTime);
		
		// 客户机用户代理信息
		String userAgentString = request.getHeader("User-Agent");
		UserAgent userAgent = UserAgent.parseUserAgentString(userAgentString);
		visitLog.setUserAgent(userAgentString);
		
		// 客户机设备类型
		OperatingSystem operatingSystem = userAgent.getOperatingSystem();
		DeviceType deviceType = operatingSystem.getDeviceType();
		String userDevice = deviceType.getName();
		visitLog.setUserDevice(userDevice);

		// 客户机操作系统名称
		String userOsName = operatingSystem.getName();
		visitLog.setUserOsName(userOsName);
		
		// 客户机浏览器
		String userBrowser = userAgent.getBrowser().getName();
		visitLog.setUserBrowser(userBrowser);

		// 客户机浏览器版本
		String userBrowserVersion = userAgent.getBrowserVersion().getVersion();
		visitLog.setUserBrowserVersion(userBrowserVersion);
		
		return visitLog;
	}
	
	/**
	 * 获取来源类型（4为旧访客，其余其它均为新访客）
	 * @param currentPageVisitTime  当前页面访问时间
	 * @param lastPageVisitTime  上次页面访问时间
	 * @return 1直接访问，这次与上次的访问时间大于30分      2来自搜索引擎      3外部链接      4内部访问，这次与上次访问的时间小于30分  <br/>
	 * 【直接访问】：也称为“直达”，带来的流量是指网民通过在浏览器上直接输入网址或者通过点击收藏的网站地址对您网站进行的访问。<br/>
	 * 			所有无法知道来源的流量都会被归入直接访问，包括点击QQ的聊天链接进入网站等。<br/>
	 * 【搜索引擎】：带来的流量是指网民通过在搜索引擎上输入搜索词后，在搜索结果页点击链接并访问您的网站，点击【搜索引擎】报告可以查看具体是哪些搜索引擎带来的流量。<br/>
	 * 			注意： 1  搜索引擎的流量是必须有搜索词的，无搜索词的流量则归为外部链接。比如网民打开百度，但是没有在百度搜索，<br/>
	 * 				     而是直接从百度首页的个人导航点击链接进入网站，则此时对这个网站而言，百度就是外部链接。<br/>
	 * 				2  来源之一的搜索引擎，如百度，是包含知道、百科等小频道的搜索流量的。<br/>
	 * 【外部链接】：带来的流量是指网民通过除搜索引擎以外的网站上的链接访问您的网站，点击【外部链接】报告可以查看具体是哪些网站带来的流量。<br/>
	 */
	private String getSourceType(VisitLog visitLog, long currentPageVisitTime, long lastPageVisitTime) {
		// 如果来源地址为空，或者是内部访问
		if (StringUtils.isBlank(visitLog.getSourceReferer()) 
				|| (this.isIncludedInDomain(visitLog.getRequestUrlHost()) 
				&& this.isIncludedInDomain(visitLog.getSourceRefererHost()))) {
			// 直接访问，这次与上次的访问时间 大于30分
			if (currentPageVisitTime - lastPageVisitTime > this.VISIT_DURATION) {
				return "1";
			}
			// 内部访问，这次与上次的访问时间 小于30分
			else {
				return "4";
			}
		} else {
			for (int i = 0; i < this.SEARCH_ENGINE_LIST.length; i++) {
				Pattern p = Pattern.compile("(^|\\.)" + this.SEARCH_ENGINE_LIST[i][1].replaceAll("\\.", "\\\\."));
				Matcher m = p.matcher(visitLog.getSourceRefererHost());
				if (m.find()) {
					String searchWord = this.getQueryValue(visitLog.getSourceReferer(), this.SEARCH_ENGINE_LIST[i][2]);
					visitLog.setSearchWord(searchWord);
					// 来自搜索引擎（必须有关键词）
					if (searchWord != "" || this.SEARCH_ENGINE_LIST[i][0] == "2" || this.SEARCH_ENGINE_LIST[i][0] == "14"
							|| this.SEARCH_ENGINE_LIST[i][0] == "17") {
						String searchEngine = this.SEARCH_ENGINE_LIST[i][0];
						visitLog.setSearchEngine(searchEngine);
						return "2";
					}
				}
			}
			// 外部链接
			return "3";
		}
	}

	private String getQueryValue(String url, String key) {
		Pattern p = Pattern.compile("(^|&|\\?|#)(" + key + ")=([^&#]*)(&|$|#)");
		Matcher m = p.matcher(url);
		if (m.find()) {
			return m.group(3);
		} else {
			return "";
		}
	}

	private boolean isSubDomain(String subDomain, String domain) {
		subDomain = "." + subDomain.replaceAll(":\\d+", "");
		domain = "." + domain.replaceAll(":\\d+", "");
		int index = subDomain.indexOf(domain);
		return (index > -1 && index + domain.length() == subDomain.length());
	}

	private String getCookie(String key) {
		String value = "";
		Cookie[] cookies = this.request.getCookies();
		if (cookies != null) {
			for (int i = 0; i < cookies.length; i++) {
				if (cookies[i].getName().equals(key)) {
					value = cookies[i].getValue();
				}
			}
		}
		return value;
	}

	private void setCookie(String key, String value) {
		this.setCookie(key, value, -1);
	}

	private void setCookie(String key, String value, int duration) {
		Cookie cookie = new Cookie(key, value);
		if (duration != -1) {
			cookie.setMaxAge(duration);
		}
		cookie.setPath("/");
		String hostName = this.request.getServerName();
		for (int i = 0; i < this.domainName.length; i++) {
			if (this.isSubDomain(hostName, this.domainName[i])) {
				hostName = this.domainName[i];
			}
		}
		cookie.setDomain(hostName);
		this.response.addCookie(cookie);
	}

	private boolean isIncludedInDomain(String hostName) {
		for (int i = 0; i < this.domainName.length; i++) {
			if (this.isSubDomain(hostName, this.domainName[i])) {
				return true;
			}
		}
		return false;
	}

	public void setDomainName(String... domainName) {
		this.domainName = domainName;
	}
	
}